from AppKit import *
from Foundation import NSUnionRect, NSInsetRect
from DBFrame import DBFrame
from DBCursor import getCursorWidth_color_
import NSColorExt
from PyObjCTools import NibClassBuilder
        

black = NSColor.blackColor()

svg_template =  '''<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<style type="text/css">
   <![CDATA[
   svg{
     width: 100%%;
     height: 100%%;
   }
   path{
     fill: white;
     stroke-linejoin: round;
     stroke-linecap: round;
   }
   ]]>
</style>
<defs>%s</defs>
<use xlink:href="#frame00000" x="0" y="0" id="drawingBoard">
    <animate attributeName="xlink:href" values="%s" attributeType="XML" dur="0.8s" begin="drawingBoard.load" repeatCount="1"/>
</use>
</svg>'''


class DBCanvasView(NibClassBuilder.AutoBaseClass):
    # the actual base class is NSView
    
    initialized = False
    current_frame = None
    playing = False
    current_idx = 0
    initial_state = None
    _simpleClick = True
    _document = None
    frames = []
    _showsFrameBounds = False
    _showsCanvasBounds = False
    _timerPeriod = 0.05
    
    def initWithFrame_(self, frame):
        self = super(DBCanvasView, self).initWithFrame_(frame)
        self.default()
        return self        

    def awakeFromNib(self):
        # print 'DBCanvasView.awakeFromNib called'
        if self.initial_state:
            self.loadData()
        else:
            self.default()
        
    def default(self):
        self.current_frame = DBFrame()
        self.frames = [self.current_frame]
        self.current_idx = 0
        self.initialized = True
        
    def setDocument_(self, document):
        self.document = document
        
    def size(self):
        return self.frame().size
        
    def height(self):
        return self.size().height
        
    def width(self):
        return self.size().width
        
    def fps(self):
        return 1.0 / self._timerPeriod
        
    def setFps_(self, value):
        self._timerPeriod = 1.0 / value

    def bounds(self):
        '''Return the bounding rect of all lines drawn in all frames, i.e., the area actually animated'''
        if self.frames:
            return reduce(NSUnionRect, [f.bounds() for f in self.frames])
        else:
            return (0,0),(0,0)
            
    def frameBounds(self):
        if self.current_frame:
            return self.current_frame.bounds()
        else:
            return (0,0),(0,0)
            
    def frameImage(self):
        return self.current_frame.image
        
    def frameImages(self):
        for frame in self.frames:
            if not frame.image:
                frame.initImage_(self)
            yield frame.image

    def translateXBy_yBy_(self, x, y):
        for frame in self.frames:
            frame.translateXBy_yBy_(x,y)
        self.setNeedsDisplay_(True)

    def scaleXBy_yBy_(self, xScale, yScale):
        for path in self.paths:
            path.scaleXBy_yBy_(xScale, yScale)
        self.setNeedsDisplay_(True)
            
    def translateFrameXBy_yBy_(self, x, y):
        if self.current_frame:
            self.current_frame.translateXBy_yBy_(x, y)
        self.setNeedsDisplay_(True)
            
    def scaleFrameXBy_yBy_(self, x, y):
        if self.current_frame:
            self.current_frame.scaleXBy_yBy_(x,y)
        self.setNeedsDisplay_(True)
        
    def frameCount(self):
        return u'Frame %d of %d' % (self.current_idx + 1, len(self.frames))
        
    def setShowsFrameBounds_(self, trueorfalse):
        self._showsFrameBounds = trueorfalse
        self.setNeedsDisplay_(True)
        
    def setShowsCanvasBounds_(self, trueorfalse):
        self._showsCanvasBounds = trueorfalse
        self.setNeedsDisplay_(True)
        
    def showsFrameBounds(self):
        return self._showsFrameBounds
        
    def showsCanvasBounds(self):
        return self._showsCanvasBounds
        
    def drawRect_(self, rect):
        NSColor.whiteColor().set()
        NSRectFill(rect)
        if not self.playing:
            if self.current_idx > 0:
                prev_frame = self.frames[self.current_idx - 1]
                prev_frame.drawRect_onCanvas_withOpacity(rect, self, 0.5)
        if self.current_frame:
            self.current_frame.drawRect_onCanvas_withOpacity(rect, self, 1.0)
            if self.showsFrameBounds():
                NSBezierPath.setDefaultLineWidth_(0.5)
                NSColor.greenColor().set()
                NSBezierPath.strokeRect_(NSInsetRect(self.current_frame.bounds(), -1, -1))
        if self.showsCanvasBounds() and not self.playing:
            NSBezierPath.setDefaultLineWidth_(0.5)
            NSColor.purpleColor().set()
            NSBezierPath.strokeRect_(NSInsetRect(self.bounds(), -2, -2))
    
    def resetCursorRects(self):
        self.addCursorRect_cursor_(self.visibleRect(), getCursorWidth_color_(self.penwidth(), self.pencolor()))
        
    def setCursor(self):
        self.window().invalidateCursorRectsForView_(self)
      
    def acceptsFirstMouse_(self, event):
        return True
        
    def acceptsFirstResponder(self):
        return True
        
    def isOpaque(self):
        return True
              
    def addFrame(self, *data):
        self.current_frame = DBFrame(*data)
        self.current_idx += 1
        self.frames.insert(self.current_idx, self.current_frame)
        if not data:
            self.document.updateChangeCount_(NSChangeDone)
            self.setNeedsDisplay_(True)
                
    def addFrameFromCurrentFrame(self):
        self.document.updateChangeCount_(NSChangeDone)
        self.current_frame = self.current_frame.copy()
        self.current_idx += 1
        self.frames.insert(self.current_idx, self.current_frame)
        self.setNeedsDisplay_(True)
        
    def removeFrame(self):
        if len(self.frames) < 2: return
        self.frames.remove(self.current_frame)
        if len(self.frames) <= self.current_idx:
            self.current_idx -= 1
        self.current_frame = self.frames[self.current_idx]
        self.setNeedsDisplay_(True)
        
    def mouseUp_(self, event):
        if self._simpleClick:
            self.document.updateChangeCount_(NSChangeDone)
            self.lineToX_Y_(*self.pointFromEvent(event))
        
    def mouseDown_(self, event):
        self.moveToX_Y_(*self.pointFromEvent(event))
        self._simpleClick = True
        
    def mouseDragged_(self, event):
#        self.window().setDocumentEdited_(True)
        self.document.updateChangeCount_(NSChangeDone)
        self.lineToX_Y_(*self.pointFromEvent(event))
        self._simpleClick = False
                
    def pointFromEvent(self, event):
        return self.convertPoint_fromView_(event.locationInWindow(), None)
        
    def moveToX_Y_(self, x, y):
        self.current_frame.moveTo((x, y))
        
    def lineToX_Y_(self, x, y):
        self.current_frame.lineTo((x, y), self)

    def pencolor(self):
        return self.document.pencolor()
        
    def penwidth(self):
        return self.document.penwidth()
        
    def lastPenColor(self):
        if self.frames:
            return self.frames[-1].lastPenColor()
        else:
            return None
    
    def lastPenWidth(self):
        if self.frames:
            return self.frames[-1].lastPenWidth()
        else:
            return None
                
    def undoLine(self):
        self.current_frame.undoLine(self)
        
    def undoSegment(self):
        self.current_frame.undoSegment(self)
        
    def redoLine(self):
        self.current_frame.redoLine(self)
        
    def redoSegment(self):
        self.current_frame.redoSegment(self)
            
    def firstFrame(self):
        if self.current_idx > 0:
            self.current_idx = 0
            self.current_frame = self.frames[self.current_idx]
            self.display()
        
    def lastFrame(self):
        self.current_idx = len(self.frames) - 1
        self.current_frame = self.frames[self.current_idx]
        self.display()
        
    def nextFrame(self):
        if self.current_idx < (len(self.frames) - 1):
            self.current_idx += 1
            self.current_frame = self.frames[self.current_idx]
            self.display()
            
    def previousFrame(self):
        if self.current_idx > 0:
            self.current_idx -= 1
            self.current_frame = self.frames[self.current_idx]
            self.display()
            
    def animate_(self, event):
        self.current_frame = self.frames[self.animation_idx]
        self.display()
        self.animation_idx += 1
        if self.animation_idx >= len(self.frames) or not self.playing:
            self.animation_timer.invalidate()
            self.animation_timer = None
            self.animation_idx = 0
            self.playing = False
            self.current_frame = self.frames[self.current_idx]
            self.display()
        
    def play_(self, sender):
        self.playing = True
        self.animation_idx = 0
        self.animation_timer = NSTimer.scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(
            self._timerPeriod,
            self,
            'animate:',
            None,
            True
        )
            
    def versionCheck_(self, version):
        pass
        #if version != 1.0:
        #    print "Error: only version 1.0 is supported"
            
    def animationData(self):
        yield 'DrawingBoardVersion(0.2)'
        for frame in self.frames:
            yield frame.frameData()
                
    def setData_(self, data):
        # print 'DBCanvasView.setData: called'
        self.initial_state = data
        self.loadData()
                
    def animationDataAsSVG(self):
        height = self.height()
        frames = '\n'.join([frame.frameDataAsSvg(height, idx) for idx, frame in enumerate(self.frames)])
        uses = ''.join(['#frame%05d;' % idx for idx in range(len(self.frames))])
        return svg_template % (frames, uses)
            
    def loadData(self):    
       # print 'DBCanvasView.loadData called'
        self.frames = []
        self.current_frame = None
        helper = DefunctFormatHelper(self.document)
        g = dict(
            DrawingBoardVersion=self.versionCheck_,
            frame=self.addFrame, 
            pen_color = helper.setPenColor,
            pen_width = self.document.setLineWidth_,
            move_to = self.moveToX_Y_,
            line_to = self.lineToX_Y_
            )
        for line in self.initial_state:
            eval(line, g)
        self.document.updateChangeCount_(NSChangeCleared)
        self.firstFrame()
        self.initialized = True
        self.setNeedsDisplay_(True)


class DefunctFormatHelper:
    def __init__(self, document):
        self.document = document

    def setPenColor(self, red, green, blue, alpha):
        self._color = NSColor.colorWithCalibratedRed_green_blue_alpha_(red, green, blue, alpha)
        self.document.changeColor_(self)
        
    def color(self):
        return self._color
        
                
    
    
